/*
 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package demo.parallelcore;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/*
 * This class demos shows usage of group Collector working with collect method.
 * @author tyan
 */

public class Grouping  extends DemoHelper  {
    /*
     * Group products by T
     * @param byXXX  a function which convert Product to T
     * @return  a map between T object to a product list, any product in this
     *          list using byXXX conversion is same to map's key
    */
    public static <T> Map<T, List<Product>> groupProduct(
            Function<Product, T> byXXX) {
        return products.parallelStream().
                //grouping product by given function
                collect(Collectors.groupingBy(byXXX));
    }
    
    /*
     * Group orders by customer with using Collectors.groupingBy(Function, Collector)
     *
     * @return map customer to its order list
    */
    public static Map<String, List<Order>> groupOrdersByCustomer() {
        //a collector that generate a order list from a customer
        Collector<Customer, List<Order>, List<Order>> collectOrder = Collector.
                of(ArrayList<Order>::new, 
                (orders, customer) -> { orders.addAll(customer.getOrders()); }, 
                (left, right) -> { left.addAll(right); return left; });
        return customers.parallelStream().
                //convert customers to a Map which key is it name, value is its
                //orders
                collect(Collectors.
                groupingBy(Customer::getCompanyName, 
                        collectOrder));      
    }
    
    /*
     * Group products by categories, only select max/min value of product
     * ordered by compFunc
     *
     * @maxBy     true  select max value product
     *            false select min value product
     * @groupFunc function provides how to group product
     * @compFunc  function provides how to sort product
     * @return 
    */
    public static <S, T extends Comparable<T>> Map<S, Optional<Product>> 
        productMaxByTGroupByS(boolean maxBy, Function<Product, S> groupFunc,
                Function<Product, T> compFunc){
        //Comparator of Product which will compare on T by given function
        Comparator<Product> comp = Comparator.comparing(compFunc);
        
        //A collector which collect max/min value by given comparator
        BiFunction<Boolean, Comparator<Product>, Collector<Product, ?, 
            Optional<Product>>> maxAndComp = (max, comparator) -> {
                if(max) 
                    return Collectors.maxBy(comparator);
                else
                    return Collectors.minBy(comparator);
            };
        return products.parallelStream().
                //collect products into a Map, which key is S by function of
                //groupFunc, value is max or min by given max and function of
                //compFunc
                collect(Collectors.groupingBy(
                    groupFunc,  
                        maxAndComp.apply(maxBy, comp)));
    }

    /*
     * Group products by R, this is demo for grouping by 
     * different element one by one.
     * @param func1  function that convert Product to R
     * @param func2  function that convert Product to T
     * @return  a map which groups products to R then T
    */
    public static <R, T> Map<R, Map<T, List<Order>>> ordersByRThenT(
        Function<Order, R> func1,Function<Order, T> func2){
        List<Order> orders = customers.parallelStream().
                //map every customer to orders
                flatMap(customer -> customer.getOrders().parallelStream()).
                //convert order to a list
                collect(Collectors.toList());
        return orders.parallelStream().
                //group products to Map<R, T> by given two functions
                collect(Collectors.groupingBy(func1, 
                Collectors.groupingBy(func2)));
    }
}
